classNameBindings: Dynamic CSS classes made easy

written by dcporter

As a mature framework, SproutCore is full of little features to make common tasks drop-dead simple – including some hidden gems. I recently added some documentation to the framework (and to the indispensable docs.sproutcore.com) for one of them, and wanted to highlight it here on the blog too.

Dynamically adding and removing CSS classes on a view is a common need — for example, to swap background images when a property changes. But there’s no obvious way to do this; I burned my share of time trying to crack it manually, and I’ve watched other devs do the same. The usual solution ends up being a custom renderer, but that’s messy and verbose, operates at the wrong level of abstraction, and opens the door to all kinds of bugs if you’re not intimately familiar with the render API.

Enter classNameBindings. It’s is a quirky but powerful little property that makes dynamic class names a breeze. (No relation to the convenience key format fooBinding, by the way.) Here’s what classNameBindings looks like:

classNameBindings: ['isRed:my-red-view', 'isUpsideDown'], isRed: YES, isUpsideDown: NO

The classNameBindings property is an array of strings. My first entry here is in the format ‘localProperty:mediated-css-property’ – the class ‘my-red-view’ will be appended to the view’s layer whenever the local property isRed is true, and will be removed if isRed turns false (or null or undefined). If you don’t specify a class, then the property name itself will be dasherized and used; so in my second entry, the local property ‘isUpsideDown’ will mediate the class ‘is-upside-down’. It just works!

You can get fancy with it, too. (Here be dragons.) If your property’s value is not strictly boolean, that value will be used as the mediated class name instead. For example:

classNameBindings: ['ketchupClass'], hasKetchup: false, hasKetchupBinding: SC.Binding.oneWay('MyApp.condimentController.ketchup'), ketchupClass: function() { if (this.get('hasKetchup')) return 'is-covered-in-ketchup'; else return 'ketchup-free'; }.property('hasKetchup').cacheable()

By returning a string, the ketchupClass property can directly control what class gets injected into the DOM. Be careful with this, though: numbers will be appended verbatim, and objects will be stringified, leading to unintended results like class="4" and class="object Object".

A couple footnotes. First, classNameBindings are implemented, not surprisingly, with some hidden KVO – which is fine as long as you aren’t in a rapidly-scrolling CollectionView, in which case you should reduce your reliance on this and all other bindings. Second, if you specify classNameBindings on a subclass, it stacks with superclass values rather than overriding, just like displayProperties and classNames do. Finally, if you want to hook up dynamic DOM attributes instead of classes, go experiment with the closely-related attributeBindings property.

That’s all for now. Be well, write beautiful code, and contribute back.

SproutCore 1.9.2 Released

written by tkeating

We are pleased to announce the immediate release of SproutCore 1.9.2.  This release contains important fixes to the 1.9 code-base and is fully backwards compatible.  We recommend that all users of SproutCore upgrade to this latest version in order to have the best development experience.  To upgrade to the latest version, simply run:

gem update sproutcore

This is a patch release and contains the following bug fixes:

Build Tools

  • We’ve softened the build tools dependency requirements from being ultra-pessimistic (i.e. within the minor version) to being just pessimistic (i.e. within the major version).  This helps to prevent Bundler conflicts.

  • Fixes the snake case generator for ‘sproutcore gen’, so that names like ‘SCProject’ get properly transformed to ‘sc_project’ and not ‘s_c_project’.

  • Fixes the ‘$repeat: repeat’ property used with the @slice SCSS directive when generating the @2x version.  It was incorrectly appending @2x to the end of the whole path (ex. /resources/images/image-sliced-from.png@2x) instead of inserting it before the extension (ex. /resources/images/image-sliced-from@2x.png).

  • Fixes incorrectly named “responder” generator to “state” generator for generating SC.State subclasses.  You can now generate an SC.State file using ‘sproutcore gen state’ or ‘sc-gen state’.

  • Added support for un-prefixed background-size CSS attribute when spriting.  This allows spriting to work properly in retina Firefox.

  • Fixes inconsistencies and improper syntax in several templates created with ‘sproutcore gen’.  Generated files are now cleaner and provide more descriptive help comments.

  • Fixes missing stylesheet warnings on a clean app generated with ‘sproutcore gen app’ or ‘sproutcore gen statechart_app’ by adding a default stylesheet to the app. Also adds a default stylesheet to a design, when using ‘sproutcore gen design’ (i.e. creating an SC.Page resource).

Framework

  • Fixes regression in IE7 and IE8 which caused XHR requests to fail to notify.

  • Fixes improper binary search used by SC.ManyArray.prototype.addInverseRecord that resulted in an infinite loop when using addInverseRecord with an orderBy function.

  • Fixes bug that allowed the context menu to appear regardless of overriding contextMenu in a view or setting SC.CONTEXT_MENU_ENABLED (globally) or isContextMenuEnabled (on a view) to false.  This makes the context menu event handling behave the same as the key, mouse, etc. event handling.

  • Fixes actions: insertNewLine, deleteForward, deleteBackward, moveLeft, moveRight, selectAll, moveUp and moveDown to be always handled by the SC.TextFieldView element when it has focus.

  • Fixes the hint value for SC.LabelView so that it will appear when the label has no value and isEditable is true.

  • No longer modifies the underlying items given to an SC.SegmentedView directly so that we don’t dirty the original objects.

  • Fixes debug files being included in builds. These files (one of which is a 2.5MB test image) would get included into every build, because they were at the wrong path.  These files should not ever be loaded in an app, but if you created an app manifest based on the contents of the built static directory, you could invariably cause the client to download over 2.5MB of files that are never used.

  • Fixes determination of touch support in Chrome on Win 8.

  • Adds missing un-prefixed border-radius rules to the default theme for browsers that have dropped the prefix.

As always, every bug fix includes an accompanying unit test to ensure that the bug does not re-appear in the future.

Dispatches From the Edge: Super Fast Collections

written by tkeating

One of the most advanced changes coming in 1.10 is the formalization of a major enhancement to SproutCore’s collection views.  Some of you may have heard of, used or tried to use the SC.CollectionFastPath mixin which gives SproutCore’s collection views, namely SC.ListView and SC.GridView, a massive performance boost.  The performance boost comes from pooling the item view objects, pooling item view layers (i.e. elements) and re-positioning layers using layout styles without modifying the DOM tree.  By re-using objects and elements, we can increase the speed that our collections can update, making gigantic lists perform like butter, even on mobile.

However, using SC.CollectionFastPath was unwieldy and difficult to get working correctly.  Turning it on was not enough, you also had to provide a couple properties on your item view class that were totally undocumented.   Due to the fact that SC.CollectionView is already optimized to create item view objects and layers only for the visible portion of the collection, it could be hard to even be sure that the fast path code was active or not.  This is all changing in 1.10.

We believe that every SproutCore view should be as fast as possible out-of-the-box on every platform and so we’re making these improvements a part of SC.CollectionView directly.  This means that by default, SC.ListView and SC.GridView will be fully optimized without any further configuration.  That said, when creating custom item views, you should properly support render and update.  Since view performance is so important, you should already know how to do this and if you need a good example, check out the custom item views in the new demo in the SproutCore Showcase.  This demo was created to debug this new technology as well as to demonstrate working with gigantic lists.

For now, the feature is still being tested and fine-tuned and any additional real-world feedback is appreciated.  So please check it out on the SproutCore master branch and bring any issues forward so that they can be addressed before release.

Thanks!

For discussion, please use sproutcore@googlegroups.com or #sproutcore on IRC.

Note:  Whereas, before you needed to set properties to turn this on, you have to set properties to turn it off right now.  If for whatever reason, you don’t want to pool elements, you can set isLayerReusable to false on your custom item view and if you don’t want to pool views, you can set isReusable to false as well.

Dispatches From the Edge: Invoking "Next"

written by tkeating

As the 1.10 release nears completion, I thought I’d better start writing about some of the many improvements now, lest the final release blog post would take me two days to write.  A lot of the changes, including today’s topic, were the result of working on big feature additions and discovering that more support was needed closer to the core.  Which brings me to the topic of this post:  SC.RunLoop.prototype.invokeNext.

On the surface, I thought invokeNext worked as is.  According to the documentation, a function passed into invokeNext would be run at the beginning of the next run of the run loop.  This differs slightly from using invokeLast, which would run the function at the end of the current run of the run loop.  This slight difference between invokeLast and invokeNext can be incredibly important though.  By using invokeNext, the current execution block can complete, giving the browser a chance to process queued events and timers and to repaint and reflow.  This break in execution is what makes invokeNext the best way to defer expensive DOM operations or in my case, the best way to ensure that an element’s style is up-to-date before modifying it (more on this in a later post).

There was just one little problem though and that was that nothing actually guaranteed that another run of the run loop would occur.  If no event arrived or timer fired, the invokeNext functions would not get called.  And when another run did occur it might be 10 milliseconds or 10 seconds after the previous run completed.  And so after some thought, I decided that invokeNext should do just that, run immediately next to the current run of the run loop.

Just a note to those that may have tried to get the same effect using invokeLater with a small time interval like 1ms.  A word of caution, you should **_only_****__** use invokeLater if you want something to occur later and anything less than 100ms is not really "later".  With such a small interval, the invokeLater timer will likely expire before the current run of the run loop even completes meaning that the functions passed to invokeLater will actually get wrapped up within the _current_ execution block.  Attempting to guess an interval long enough for the current execution to complete, but not too long to annoy the user, say an interval of 50 or 100ms, is a fool's game.

What you’ll find on master is that using invokeNext now allows the current run of the run loop (i.e.  JavaScript execution) to complete before immediately starting one additional run.  If another run of the run loop was already scheduled to occur at the same time via an expiring SC.Timer, still only one more run would occur.  So no worries there.

So apologies for the low-level nature of this update, I promise that the next ones will be more interesting.  For what it’s worth, now that invokeNext fills the hole between invokeLast and invokeLater properly, I’m extremely excited about what else I can do with it.

Cheers!

For discussion, please use sproutcore@googlegroups.com or #sproutcore on IRC.

SproutCore 1.9.1 Released

written by admin

We are pleased to announce the immediate release of SproutCore 1.9.1.  This release contains important fixes to the 1.9 code-base and is fully backwards compatible.  We recommend that all users of SproutCore upgrade to this latest version in order to get the best development experience.  To upgrade to the latest version, simply run:

gem update sproutcore

This is a patch release and contains the following bug fixes:

  • Fixes a bug that left childView elements in the DOM when they were rendered as part of their parent’s layer and the child was later removed.

  • Fixes improper implementation of SC.SelectionSet:constrain() that prevented the last object in the set from being constrained.

  • Fixes minor memory leak due to implicit globals in SC.MenuPane.

  • Fixes memory leak with child views of SC.View. The ‘owner’ property prevented views from being able to be garbage collected when they were destroyed.

  • Fixes SC.stringFromLayout() to include all the layout properties.

  • Fixes the excess calling of parentViewDidResize() on child views when the view’s position changes, but it’s size doesn’t.

  • Fixes an issue that occurs with SC.ImageView’s viewDidResize implementation, where it fails to resize appropriately.

  • Fixes a bug in SC.Locale that caused localizations to be overwritten by the last language localized.

  • Fixes SC.Request’s application of the Content-Type header. It was incorrectly adding the header for requests that don’t have a body which would cause some servers to reject GET or DELETE requests.

  • Fixes a bug where SC.Record relationships modified within a nested store, would fail to propagate the changes to the parent store if isMaster was NO in the toOne or toMany attribute.

As always, every bug fix includes an accompanying unit test to ensure that the bug does not re-appear in the future.  For further details, please view the complete Change Log.